#pragma once
#include "V2D.h"

/** standard Rect using floats */
#define RectF	Rect<float>

/**
 * a rectangle class that can be interfaced as a x,y,w,h rectangle as well as a
 * 2-corners rectangle (min, max):
 *
 * minx,miny
 * ^--W--+
 * |     |
 * H     |
 * | maxx,maxy
 * +-----^
 */
template<typename VTYPE>
struct Rect
{
	/** position */
	V2D<VTYPE> p;
	/** dimension */
	V2D<VTYPE> d;
	/**
	 * given min and max points, makes this a valid inscribed rectangle
	 * @param a_min minimum x/y coordinate of the rectangle
	 * @param a_max maximum x/y coordinate of the rectangle
	 */
	inline void set(const V2D<VTYPE> & a_min, const V2D<VTYPE> & a_max)
	{
		p.set(a_min);
		d.set(a_max.difference(a_min));
	}

	/** given points in arbitrary order, makes this an inscribed rectangle */
	void setFromPoints(const V2D<VTYPE> & a, const V2D<VTYPE> & b)
	{
		V2D<VTYPE> min(((a.getX()<b.getX())?a.getX():b.getX()),((a.getY()<b.getY())?a.getY():b.getY()));
		V2D<VTYPE> max(((a.getX()>b.getX())?a.getX():b.getX()),((a.getY()>b.getY())?a.getY():b.getY()));
		set(min, max);
	}

	/** creates a valid rectangle inscribed into two points in arbitrary order */
	Rect(const V2D<VTYPE> & corner_a, const V2D<VTYPE> & corner_b)
	{
		setFromPoints(corner_a,corner_b);
	}

	/** initialization method */
	inline void set(const VTYPE & a_x, const VTYPE & a_y, const VTYPE & a_w, const VTYPE & a_h)
	{p.set(a_x,a_y);d.set(a_w,a_h);}

	/** sets the rectangle to be all zeros (zero size at the origin) */
	inline void clear(){set(0,0,0,0);}

	/** default constructor creates an invalid rectangle (zero size) at the origin */
	Rect(){clear();}

	/** complete constructor */
	Rect(const VTYPE & x, const VTYPE & y, const VTYPE & w, const VTYPE & h){set(x,y,w,h);}

	/** copy method */
	void set(const Rect & r){p=r.p;d=r.d;}
	/** @return x position of rectangle (left edge location) */
	inline VTYPE getX() const {	return p.getX();	}
	/** @return y position of rectangle (top edge location) */
	inline VTYPE getY() const {	return p.getY();	}
	/** @return left edge location */
	inline VTYPE getMinX() const {	return p.getX();	}
	/** @return right edge location */
	inline VTYPE getMaxX() const {	return p.getX()+d.getX();	}
	/** @return top edge location */
	inline VTYPE getMinY() const {	return p.getY();	}
	/** @return bottom edge location */
	inline VTYPE getMaxY() const {	return p.getY()+d.getY();	}
	/** @return upper-left corner */
	inline V2D<VTYPE> getMin() const {	return V2D<VTYPE>(getMinX(),getMinY());	}
	/** @return lower-right corner */
	inline V2D<VTYPE> getMax() const {	return V2D<VTYPE>(getMaxX(),getMaxY());	}
	/** @return width */
	inline VTYPE getWidth() const {	return d.getX();	}
	/** @return height */
	inline VTYPE getHeight() const{	return d.getY();	}
	/** set the x position value (moves the rectangle) */
	inline void setX(const VTYPE & a_value){	p.setX(a_value);	}
	/** set the y position value (moves the rectangle) */
	inline void setY(const VTYPE & a_value){	p.setY(a_value);	}
	/** set the width value (moves the max value) */
	inline void setWidth(const VTYPE & a_value){	d.setX(a_value);	}
	/** set the height value (moves the max value) */
	inline void setHeight(const VTYPE & a_value){	d.setY(a_value);	}
	/** set the left-edge location (resizes the rectangle) */
	inline void setMinX(const VTYPE & a_value){	d.addX(getX()-a_value);p.setX(a_value);	}
	/** set the right-edge location (resizes the rectangle) */
	inline void setMaxX(const VTYPE & a_value){	d.setX(a_value-getX());	}
	/** set the top-edge location (resizes the rectangle) */
	inline void setMinY(const VTYPE & a_value){	d.addY(getY()-a_value);p.setY(a_value);	}
	/** set the bottom-edge location (resizes the rectangle) */
	inline void setMaxY(const VTYPE & a_value){	d.setY(a_value-getY());	}
	/** set the upper-left corner location (resizes the rectangle) */
	inline void setMin(const V2D<VTYPE> & p){setMinX(p.x);setMinY(p.y);}
	/** set the lower-right corner location (resizes the rectangle) */
	inline void setMax(const V2D<VTYPE> & p){setMaxX(p.x);setMaxY(p.y);}

	/** gets the position of the rectangle (upper-left hand corner x/y) */
	inline V2D<VTYPE> getPosition() const {return p;}
	/** gets the dimensions of the rectangle (width/height) */
	inline V2D<VTYPE> getDimension() const {return d;}
	/** get the center coordinate of this rectangle */
	inline V2D<VTYPE> getCenter() const {return p.sum(d.quotient(2));}
	/** set the position of the rectangle (upper-left hand corner x/y, moves the rectangle) */
	inline void setPosition(const V2D<VTYPE> & p){this->p.set(p);}
	/** sets the size of the rectangle (width/height) */
	inline void setDimension(const V2D<VTYPE> & p){this->d.set(p);}
	/** move the rectangle so that it's center is at the given coordinate */
	inline void setCenter(const V2D<VTYPE> & p){setPosition(p.difference(d.quotient(2)));}

	/** @return true if the width and height are both zero */
	inline bool isZero(){return getWidth()==0 && getHeight()==0;}
	/** @return true if the width and height are both greater than one */
	inline bool isValid()const{return getWidth()>0&&getHeight()>0;}
	/** @return true if this rectangle contains the given location */
	inline bool contains(const V2D<VTYPE> & p)const
	{
		return getMinX() <= p.getX() && getMinY() <= p.getY()
			&& getMaxX() >  p.getX() && getMaxY() >  p.getY();
	}
	/** @return if this intersects (overlaps at all) the given rectangle */
	inline bool intersects(const Rect & r)const
	{
		return	!( r.getMaxX() < getMinX() || r.getMinX() > getMaxX()
				|| r.getMaxY() < getMinY() || r.getMinY() > getMaxY() );
	}
	/** @return true if this and the given rectangle have identical values */
	inline bool equals(const Rect & r){return p.equals(r.p)&&d.equals(r.d);}
	/** adds a given rectangle's area to this rectangle (may resize the rectangle) */
	void add(const Rect & r)
	{
		if(r.getMinX() < getMinX())	setMinX(r.getMinX());
		if(r.getMaxX() > getMaxX())	setMaxX(r.getMaxX());
		if(r.getMinY() < getMinY())	setMinY(r.getMinY());
		if(r.getMaxY() > getMaxY())	setMaxY(r.getMaxY());
	}
	/** makes this rectangle only the intersection with the given rectangle */
	void clip(const Rect & r)
	{
		if(r.getMinX() > getMinX())	setMinX(r.getMinX());
		if(r.getMaxX() < getMaxX())	setMaxX(r.getMaxX());
		if(r.getMinY() > getMinY())	setMinY(r.getMinY());
		if(r.getMaxY() < getMaxY())	setMaxY(r.getMaxY());
	}
	/** @return what this rectangle would be if it were clipped */
	Rect clipped(const Rect & a_clippingRect)
	{
		Rect r(*this);
		r.clip(a_clippingRect);
		return r;
	}
	/** reduces the rectangles size in all directions by the given value */
	void inset(const VTYPE & a_border)
	{set(getX()+a_border, getY()+a_border, getWidth()-a_border*2, getHeight()-a_border*2);}
	/** @return what this rectangle would be if it were inset the given value */
	Rect insetted(const VTYPE & a_border)
	{Rect insetRect(*this);insetRect.inset(a_border);return insetRect;}
	/** @return a rectangle moved over a_direction units (this rectangle is a unit-size) */
	Rect unitsOver(const V2D<VTYPE> & a_direction)
	{
		return Rect(getX()+getWidth()*a_direction.getX(),
			getY()+getHeight()*a_direction.getY(),getWidth(),getHeight());
	}
	/** add a_direction to the rectangle's position */
	void move(const V2D<VTYPE> & a_direction)
	{
		p.add(a_direction);
	}
	/** @return this rectangle if it were moved a_direction */
	Rect moved(const V2D<VTYPE> & a_direction)
	{
		Rect r(*this);
		r.move(a_direction);
		return r;
	}
	static const int BAD_VALUE = -1;
	/** used by getField() and setField() */
	static const int X = 0, Y = 1;
	/** MIN_X/MIN_Y */
	static const int MIN = 2;	//V2D::NUM_DIMENSIONS*1
	/** used by getField() and setField() */
	static const int MIN_X = MIN+X, MIN_Y = MIN+Y;
	/** MAX_X/MAX_Y */
	static const int MAX = 4;	//V2D::NUM_DIMENSIONS*2
	/** used by getField() and setField() */
	static const int MAX_X = MAX+X, MAX_Y = MAX+Y;
	/** WIDTH/HEIGHT */
	static const int DIMENSION = 6;	//V2D::NUM_DIMENSIONS*3
	/** used by getField() and setField() */
	static const int WIDTH = DIMENSION+X, HEIGHT = DIMENSION+Y;
	/** @param a_field (X, Y, MIN_X, MIN_Y, MAX_X, MAX_Y, WIDTH, HEIGHT) */
	short getField(const int & a_field) const
	{
		switch(a_field)
		{
		case X:		return getX();
		case Y:		return getY();
		case MIN_X:	return getMinX();
		case MIN_Y:	return getMinY();
		case MAX_X:	return getMaxX();
		case MAX_Y:	return getMaxY();
		case WIDTH:	return getWidth();
		case HEIGHT:	return getHeight();
		}
		return 0;
	}
	/** @param a_field (X, Y, MIN_X, MIN_Y, MAX_X, MAX_Y, WIDTH, HEIGHT) */
	void setField(const int & a_field, const VTYPE & a_value)
	{
		switch(a_field)
		{
		case X:		return setX(a_value);
		case Y:		return setY(a_value);
		case MIN_X:	return setMinX(a_value);
		case MIN_Y:	return setMinY(a_value);
		case MAX_X:	return setMaxX(a_value);
		case MAX_Y:	return setMaxY(a_value);
		case WIDTH:	return setWidth(a_value);
		case HEIGHT:	return setHeight(a_value);
		}
	}
	/** force this rectangle to overlap as much area as possible with the given rectangle */
	void keepBound(const Rect & area)
	{
		bool isSmallerThanArea;
		// go through X and Y dimensions
		for(int d = 0; d < 2; ++d)
		{
			isSmallerThanArea = getField(DIMENSION+d) < area.getField(DIMENSION+d);
			if(isSmallerThanArea
			?(getField(MIN+d) <= area.getField(MIN+d))
			:(getField(MIN+d) >= area.getField(MIN+d)))
				setField(d, area.getField(d));
			else if(isSmallerThanArea 
			?(getField(MAX+d) >= area.getField(MAX+d))
			:(getField(MAX+d) <= area.getField(MAX+d)))
				setField(d, area.getField(MAX+d)-getField(DIMENSION+d));
		}
	}
	/** force this rectangle out of the given rectangle */
	void keepOut(const Rect & area)
	{
		if(intersects(area))
		{
			for(int d = 0; d < V2D::NUM_DIMENSIONS; ++d)
			{
				if(getField(MAX+d) > area.getField(MIN+d) 
				&& getField(MIN+d) < area.getField(MIN+d))
					setField(d, area.getField(MIN+d)-getField(DIMENSION+d));
				if(getField(MIN+d) < area.getField(MAX+d)
				&& getField(MAX+d) > area.getField(MAX+d))
					setField(d, area.getField(MAX+d));
			}
		}
	}
	/** multiply the dimensions and position of this rectangle */
	void multiply(const VTYPE & a_value){
		setX(getX()*a_value);
		setY(getY()*a_value);
		setWidth(getWidth()*a_value);
		setHeight(getHeight()*a_value);
	}
	/** expand the rectangle in the given direction */
	void expand(const V2D<VTYPE> & a_direction)
	{
		int delta, whichField;
		for(int d = 0; d < V2D::NUM_DIMENSIONS; ++d)
		{
			if((delta = a_direction.getField(d)))
			{
				whichField = (delta>0)?MAX:MIN;
				setField(whichField+d, getField(whichField+d)+delta);
			}
		}
	}
	/**
	 * @param a_range {Rect::X,Rect::Y}
	 * @return true if this and the given rectangle share the passed in range
	 * (x or y), meaing their width/height would overlap
	 */
	inline bool isCommonRange(Rect * a_him, int a_range)
	{
		return getField(MAX+a_range) >= a_him->getField(MIN+a_range)
			&& getField(MIN+a_range) <= a_him->getField(MAX+a_range);
	}

	/**
	 * @param a_myEdge {MINX,MINY,MAXX,MAXY}
	 * @return true if this given edge touches the given rectangle's given edge
	 * (is within the given range)
	 */
	bool isEdgeTouchingEdge(int a_myEdge, Rect * a_him, int a_hisEdge, VTYPE a_touchRange = 1.0/1024)
	{
		return (isCommonRange(a_him, a_myEdge % V2D<VTYPE>::NUM_DIMENSIONS)
			&& (abs(getField(a_myEdge) - a_him->getField(a_hisEdge)) < a_touchRange));
	}

	/** @param a_out_4points will become populated by this rectangle as 4 lines */
	void getPoints(V2D<VTYPE> * a_out_points)const
	{
		a_out_points[0].set(getMinX(), getMinY());
		a_out_points[1].set(getMaxX(), getMinY());
		a_out_points[2].set(getMaxX(), getMaxY());
		a_out_points[3].set(getMinX(), getMaxY());
	}

	/** draws to an OpenGL context */
	void glDraw() const
	{
		V2D<VTYPE> points[4];
		getPoints(points);
		glBegin(GL_TRIANGLE_FAN);
		for(int i = 0; i < 4; ++i)
			points[i].glVertex();
		glEnd();
	}

	void glDrawOutline() const
	{
		glColor3ub(0, 0, 0);
		V2D<VTYPE> points[4];
		getPoints(points);
		glBegin(GL_LINE_LOOP);
		for(int i = 0; i < 4; ++i)
			points[i].glVertex();
		glEnd();
	}
};
